package com.yb.core.aop;

import cn.hutool.core.util.ClassUtil;
import com.yb.core.annotation.Decrypt;
import com.yb.core.annotation.repeatable.Decrypts;
import com.yb.core.util.DecryptUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Copyright (C), 2022-2022, 姚兵
 * Author: 32210
 * Date: 2022/12/9 16:06
 * FileName: DesensitizationAspect
 * Description:
 */
@Aspect
@Configuration
@EnableAspectJAutoProxy
@Slf4j
@Order(Ordered.LOWEST_PRECEDENCE-1)
public class DecryptAspect {

    @Autowired
    DecryptUtil decryptUtil;

    LocalVariableTableParameterNameDiscoverer localVariableTable = new LocalVariableTableParameterNameDiscoverer();


    @Around("execution(public * com.yb.*..controller.*.*(..)) && (@annotation(com.yb.core.annotation.Decrypt) || @annotation(com.yb.core.annotation.repeatable.Decrypts))")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        Annotation[] annotations = method.getAnnotations();
        Root root = new Root();
        String[] paraNameArr = localVariableTable.getParameterNames(method);
        Map<String, Object> map = new LinkedHashMap<>();
        for(int i=0;i<paraNameArr.length;i++) {
            map.put(paraNameArr[i],joinPoint.getArgs()[i]);
        }

        root.setParamMap(map);

        root=handleParam(root,method);

        Arrays.stream(annotations).forEach(item->{
            log.info("annotation:{}",item.toString());
        });

        Object result = joinPoint.proceed(root.getParamMap().values().toArray(new Object[0]));
        
        return result;
    }


    @Data
    class Root{
        Map<String,Object> paramMap=new LinkedHashMap<>();
    }

    private Root handleParam(Root root,Method method){

        if(method.isAnnotationPresent(Decrypts.class)){
            this.handle(root,method.getAnnotation(Decrypts.class));
        }else if(method.isAnnotationPresent(Decrypt.class)){
            this.handle(root,method.getAnnotation(Decrypt.class));
        }

        return root;
    }

    private void handle(Root object, Decrypts decrypts){
        Decrypt[] values = decrypts.value();
        Arrays.stream(values).forEach(item->this.handle(object,item));
    }

    private void handle(Root object, Decrypt decrypt){

        try {
            setFields(object, decrypt);
        } catch (Exception e){
            log.error("{}",e);
        }

    }

    private void setFields(Root root, Decrypt decrypt) throws IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        Set<String> fields = Arrays.stream(decrypt.fields()).collect(Collectors.toSet());
        setFields(root.getParamMap(),decrypt,fields);

    }

    private void setMap(Object object, Decrypt decrypt, Set<String> fields) throws IntrospectionException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        log.info("object is Map");
        Map map= (Map) object;
        Set<Map.Entry> entrySet = map.entrySet();
        Iterator<Map.Entry> iterator = entrySet.iterator();
        while (iterator.hasNext()){
            Map.Entry next =  iterator.next();
            Object key = next.getKey();
            Object value = next.getValue();
            if(Objects.isNull(value))continue;
            if(fields.contains(key)&&value instanceof String){
                map.put(key,decryptUtil.decrypt((String) value, decrypt.algorithm()));
                continue;
            }
            setFields(value, decrypt,fields);
        }
        return;
    }

    private void setSet(Object object, Decrypt decrypt, Set<String> fields) throws IntrospectionException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        log.info("object is set");
        Set set= (Set) object;
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            Object value = iterator.next();
            if(Objects.isNull(value))continue;
            setFields(value, decrypt,fields);
        }
        return;
    }


    private void setList(Object object, Decrypt decrypt, Set<String> fields) throws IntrospectionException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        List list= (List) object;
        for (Object item:list) {
            setFields(item, decrypt,fields);
        }
    }

    private void setArray(Object object, Decrypt decrypt, Set<String> fields) throws IntrospectionException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        Class<?> componentType = object.getClass().getComponentType();
        int length = Array.getLength(object);
        for(int i=0;i<length;i++){
            if(ClassUtil.isBasicType(componentType))continue;
            setFields(Array.get(object,i), decrypt,fields);
        }
    }

    private void setObject(Object object, Decrypt decrypt, Set<String> fields) throws IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        log.info("object is Object");
        BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());

        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

        // 遍历属性列表，查找指定的属性
        if (propertyDescriptors != null && propertyDescriptors.length > 0) {
            for (PropertyDescriptor propDesc : propertyDescriptors) {
                if(propDesc.getPropertyType().equals(String.class)){
                    if (fields.contains(propDesc.getName())) {
                        Method methodSetUserName = propDesc.getWriteMethod();
                        Method readMethod = propDesc.getReadMethod();
                        Object value = readMethod.invoke(object);
                        if(Objects.nonNull(value)) {
                            methodSetUserName.invoke(object, decryptUtil.decrypt((String) value, decrypt.algorithm()));  // 写入属性值
                        }
                        log.info("set {}",propDesc.getName());
                        break;
                    }
                }else{
                    setFields(ClassUtil.getDeclaredField(object.getClass(),propDesc.getName()), decrypt,fields);
                }
                // 找到则写入属性值
            }
        }
    }

    private void setFields(Object object, Decrypt decrypt, Set<String> fields) throws IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {

        if(Objects.isNull(object))return;

        if(ClassUtil.isBasicType(object.getClass())){
            return;
        }

        if(object instanceof Map){
            this.setMap(object, decrypt,fields);
            return;
        }

        if(object instanceof Set){
            this.setSet(object, decrypt,fields);
        }

        if(object instanceof List){
            this.setList(object, decrypt,fields);
            return;
        }

        if(object.getClass().isArray()){
            this.setArray(object, decrypt,fields);
            return;
        }

        this.setObject(object, decrypt,fields);

    }

}
